package com.github.cdisk.xtrans.component;

import java.io.UnsupportedEncodingException;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.sql.Types;
import java.util.HashMap;
import java.util.Map;

import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;

import com.github.cdisk.xtrans.annotiation.Attribute;
import com.github.cdisk.xtrans.annotiation.Component;
import com.github.cdisk.xtrans.annotiation.ConnectLine;
import com.github.cdisk.xtrans.annotiation.DefaultLine;
import com.github.cdisk.xtrans.component.utils.SqlStruct;
import com.github.cdisk.xtrans.connect.Connect;
import com.github.cdisk.xtrans.connect.Db;
import com.github.cdisk.xtrans.core.BaseComponent;
import com.github.cdisk.xtrans.core.ConfigFile;
import com.github.cdisk.xtrans.core.GlobalVariables;
import com.github.cdisk.xtrans.exception.BuildException;
import com.github.cdisk.xtrans.exception.NoVariableNameException;

@Component(value="SQL查询", icon="table.png", group="常用元件")
public class Select extends BaseComponent {

	private static final Logger LOGGER = Logger.getLogger(Select.class);

	/** 要执行的sql语句 */
	@Attribute(value="执行SQL语句", group="执行")
	protected String sql;

	/** 元件逐条执行过程 */
	@DefaultLine
	@ConnectLine(value="逐条处理")
	@Attribute(value="每条数据的过程", group="执行")
	protected BaseComponent proccess;


	@Override
	public void build(ConfigFile config, GlobalVariables variables) throws BuildException {
		super.build(config, variables);
		try {
			sql = variables.replaceVariables(sql); // 替换变量
		} catch (NoVariableNameException e) {
			throw new BuildException(this.getClass().getSimpleName() + "(" +  this.id + ")" + e.getMessage());
		}
		if (StringUtils.isEmpty(sql)) {
			throw new BuildException(this.getClass().getSimpleName() + "(" +  this.id + ")未找到语句[sql]内容。");
		}
		if (connect == null) {
			throw new BuildException(this.getClass().getSimpleName() + "(" +  this.id + ")没有指定数据库连接[connect]。");
		}
	}

	@Override
	public int execute(Map<String, Object> input) {

		try {
			Db db = this.connect.getDb();
			db.open();

			ProccessResult rst = proccess(input); // 执行逐条处理
			if (this.success != null) {
				Map<String, Object> __map = new HashMap<String, Object>();
				__map.put("$parent", input);
				__map.put("$proccessCount", rst.count);
				__map.put("$successCount", rst.success);
				__map.put("$failedCount", rst.failed);
				__map.put("$errorCount", rst.error);
				this.success.execute(__map);
				__map.clear();
			}

			LOGGER.info("执行SQL:" + this.id + ", 执行成功.");
		} catch (SQLException e) {
			e.printStackTrace();
			return EXECUTE_ERROR;
		}

		return EXECUTE_SUCCESS;
	}

	/**
	 * 逐条处理过程
	 * @param struct
	 * @param input 
	 * @return
	 * @throws SQLException
	 */
	private ProccessResult proccess(Map<String, Object> input) throws SQLException {
		ProccessResult result = new ProccessResult();

		SqlStruct struct = new SqlStruct(this.sql, input);
		LOGGER.debug(struct);

//		int count = db.executeQueryCount(pSql.sql, pSql.param); // 本想增加count，以便评估结束时间，但发现提前count非常耗时

		Connect connect = this.connect;
		Db db = connect.getDb();

		ResultSet rs = db.executeQueryResult(struct.sql, struct.param); // 执行SQL
		if (rs == null) {
			throw new SQLException("无法获取任何结果，查看ResultSet返回为null。");
		}
		ResultSetMetaData rsmd = rs.getMetaData();
		int col_len = rsmd.getColumnCount();
		Map<String, Object> map;
		int count = 0;
		int success = 0;
		int failed = 0;
		int error = 0;
		while (rs.next()) {
			count++;
			LOGGER.debug("[" + count + "]条数据准备处理.");
			if (this.proccess != null) {
				map = new HashMap<String, Object>();
				for (int i = 1; i <= col_len; i++) {
					String key = rsmd.getColumnName(i).toLowerCase();
					int type = rsmd.getColumnType(i);
					Object value = null;
					if (Types.VARCHAR == type || Types.NVARCHAR == type || Types.LONGVARCHAR == type || Types.LONGNVARCHAR == type) {
						if (this.connect.getDriver().contains("odbc")) {
							// odbc 的字符集处理办法
							if (this.connect.getCharset() != null) {
								try {
									byte[] bytes = rs.getBytes(i);
									if (bytes == null) {
										value = null;
									} else {
										value = new String(bytes, this.connect.getCharset());
									}
								} catch (UnsupportedEncodingException e) {
									LOGGER.error("编码转换错误（： " + this.id + "）：" + e.getMessage());
								}
							} else {
								value = rs.getString(i);
							}
						} else {
							value = rs.getString(i);
						}
//					} else if (Types.DATE == type) {
//						value = rs.getDate(i);
//					} else if (Types.TIMESTAMP == type) {
//						value = rs.getTimestamp(i);
					} else {
						value = rs.getObject(i);
					}
//						if (value instanceof Clob) {
//							Clob temp = ((Clob) value);
//							int length = (int) temp.length();
//							value = temp.getSubString(1, length);
//						}
					map.put(key, value);
				}
				map.put("$parent", input);
				map.put("$index", count);
//				try {
//					ObjectMapper objectMapper = new ObjectMapper();
//					LOGGER.debug("执行数据（" + this.id + "）：" + objectMapper.writeValueAsString(map));
//				} catch (JsonProcessingException e) {
//					e.printStackTrace();
//				}
				int status = this.proccess.execute(map);
				if (status == EXECUTE_SUCCESS) success++;
				if (status == EXECUTE_FAILED) failed++;
				if (status == EXECUTE_ERROR) error++;
				map.clear();
			}
		}
		try { rs.getStatement().close(); } catch (Exception e) { }
		try { rs.close(); } catch (Exception e) { }

		result.count = count;
		result.success = success;
		result.failed = failed;
		result.error = error;

		return result;
	}

	@Override
	public boolean isConnectComponent(BaseComponent component) {
		if (super.isConnectComponent(component)) return true;
		if (proccess == component) return true;
		return false;
	}

	/**
	 * 设置逐条执行的元件
	 * @return
	 */
	public BaseComponent getProccess() {
		return proccess;
	}

	/**
	 * 设置逐条执行的元件
	 * @param proccess
	 */
	public void setProccess(BaseComponent proccess) {
		this.proccess = proccess;
	}

	public String getSql() {
		return sql;
	}

	public void setSql(String sql) {
		this.sql = sql;
	}

	/**
	 * 逐条执行结果
	 * @author Cdisk
	 *
	 */
	private static class ProccessResult {

		/** 执行总数 */
		public int count;

		/** 成功总数 */
		public int success;

		/** 失败总数 */
		public int failed;

		/** 错误总数 */
		public int error;

	}

}
